package com.xs.aifitnessagent.app;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.xs.aifitnessagent.advison.MyLoggerAdvisor;
import com.xs.aifitnessagent.chatmemory.FileBasedChatMemory;
import com.xs.aifitnessagent.chatmemory.MysqlChatMemory;
import com.xs.aifitnessagent.chatmemory.RedisChatMemory;
import com.xs.aifitnessagent.entities.dto.ChatDTO;
import com.xs.aifitnessagent.rag.AppRagCustomAdvisorFactory;
import com.xs.aifitnessagent.rag.QueryRewriter;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.VectorStoreChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;

import java.util.List;
// import java.util.concurrent.atomic.AtomicInteger; // 测试阶段不需要，已注释

/**
 * 健身App
 *
 * @author lambor
 */
@Component
@Slf4j
public class FitnessApp {

    @Resource
    private VectorStore appVectorStore;

    @Resource
    private QueryRewriter queryRewriter;

    @Resource
    private Advisor appRagCloudAdvisor;

    @Resource
    private VectorStore pgVectorVectorStore;

    /** AI 调用工具能力 **/
    @Resource
    private ToolCallback[] allTools;

    /** AI调用 MCP 服务 **/
    @Resource
    private ToolCallbackProvider toolCallbackProvider;

    private final ChatClient chatClient;
    
    /** 专门用于关键词提取的ChatClient，不保存消息记录 **/
    private final ChatClient keywordExtractionClient;

    /** 提示词 —— 提取关键词 **/
    private static final String SYSTEM_PROMPT = """
            你是一个专业的关键词提取助手，负责精准识别用户问题的核心要素。请严格按以下步骤执行：
            1. **分析用户问题**：识别问题中的实体、动作、领域术语等关键要素
            2. **提取关键词**：生成3-5个最相关的关键词/短语，要求：
               - 覆盖问题核心诉求
               - 排除代词、模糊词汇
               - 用JSON格式输出：{"keywords": ["词1", "词2"...]}
            3. **禁止行为**：
               - 不要回答问题本身
               - 不要添加解释性文字
            
            示例：
            用户问："如何解决笔记本电脑突然蓝屏的问题？"
            输出：{"keywords": ["笔记本电脑", "蓝屏", "故障排除"]}
            """;

    /** 默认系统提示词 —— 系统角色设定 **/
    private static final String FITNESS_SYSTEM_PROMPT = """
            你是一位资深健身教练与运动康复专家，持有国际认证。核心目标：
            1. 通过对话了解用户健康状况
            2. 提供安全的运动方案与健身计划
            3. 培养长期健康管理意识
            
            行为准则：
            - 安全优先："无评估，不训练"
            - 主动倾听：通过开放式提问挖掘需求
            - 分层引导：基础信息→核心诉求→限制因素→目标设定
            
            对话流程：
            1. 收集基础信息：受伤史、疼痛评估、生活习惯
            2. 需求挖掘：痛点聚焦、目标拆解(康复/增肌/减脂)
            3. 方案制定：安全筛查、匹配禁忌动作、生成阶梯式计划
            4. 行为干预：知识植入、持续跟进
            
            医疗红线：出现严重症状时终止建议并引导就医
            
            输出格式要求：
            - 使用Markdown格式组织回复内容
            - 重要信息使用**粗体**强调
            - 使用标题(##)分组相关内容
            - 列表使用-或数字编号
            - 代码或专业术语使用`反引号`标记
            """;

//     /** CharMemory持久化到本地文件的存放路径 **/
//     private static final String CHAT_MEMORY_PATH = System.getProperty("user.dir") + "/chat-memory";

    /**
     * 初始化 ChatClient
     *
     */
    public FitnessApp(ChatModel dashscopeChatModel, MysqlChatMemory mysqlChatMemory, RedisChatMemory redisChatMemory) {
//        // 初始化基于内存的对话记忆
//        MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
//                .chatMemoryRepository(new InMemoryChatMemoryRepository())
//                .maxMessages(20)
//                .build();

//        FileBasedChatMemory fileBasedChatMemory = new FileBasedChatMemory(CHAT_MEMORY_PATH);

        chatClient = ChatClient.builder(dashscopeChatModel)
                .defaultSystem(FITNESS_SYSTEM_PROMPT)
                .defaultAdvisors(
//                        MessageChatMemoryAdvisor.builder(chatMemory).build(),
                        // 自定义日志 Advisor, 可按需开启
                        new MyLoggerAdvisor(),
//                        MessageChatMemoryAdvisor.builder(fileBasedChatMemory).build()
                        // 使用 MysqlChatMemory 作为主要的对话记忆存储
                        MessageChatMemoryAdvisor.builder(mysqlChatMemory).build()
                        // 如果需要使用 RedisChatMemory 作为缓存，可以在这里添加
                        // MessageChatMemoryAdvisor.builder(redisChatMemory).build()
                )
                .build();
                
        // 初始化关键词提取专用的ChatClient，不配置MessageChatMemoryAdvisor
        keywordExtractionClient = ChatClient.builder(dashscopeChatModel)
                .defaultAdvisors(new MyLoggerAdvisor())
                .build();
    }

    /**
     * AI 基础对话（支持多轮对话记忆）
     *
     */
    public String doChat(String message, String chatId) {
        ChatResponse chatResponse = chatClient
                .prompt()
                .user(message)
                .advisors(
                        advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, chatId)
                )
                .call()
                .chatResponse();
        return chatResponse.getResult().getOutput().getText();
    }

    /**
     * AI 基础对话（支持多轮对话记忆，SSE 流式传输）
     *
     */
    public Flux<String> doChatByStream(String message, String chatId) {
        return chatClient
                .prompt()
                .user(message)
                .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, chatId))
                .stream()
                .content();
    }

    public record FitnessReport(String titile, List<String> suggestings) {

    }

    /**
     * AI 生成健身报告（结构化输出）
     *
     */
    public FitnessReport doChatWithReport(String message, String chatId) {
        return chatClient
                .prompt()
                .system("每次对话都要生成健身结果，标题为{用户名}的健身报告，内容为建议列表")
                .user(message)
                .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, chatId))
                .call()
                .entity(FitnessReport.class);
    }

    /**
     * 和 RAG 知识库进行对话
     *
     */
    public String doChatWithRag(String message, String chatId) {
        // 查询重写
        String rewriteMessage = queryRewriter.doQueryRewrite(message);
        ChatResponse chatResponse = chatClient
                .prompt()
                // 使用改写后的查询
                .user(rewriteMessage)
                .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, chatId))
                // 开启日志，便于观察效果
                .advisors(new MyLoggerAdvisor())
                // 应用 RAG 知识库问答
                .advisors(new QuestionAnswerAdvisor(appVectorStore))
//                // 应用 RAG 检索增强服务（基于云知识库服务）
//                .advisors(appRagCloudAdvisor)
//                // 应用 RAG 检索增强服务（基于 PgVector 向量存储）
//                .advisors(new QuestionAnswerAdvisor(pgVectorVectorStore))
//                // 应用 RAG 自定义服务（文档查询器 + 上下文增强器）
//                .advisors(
//                        AppRagCustomAdvisorFactory.createAppRagCustomAdvisor(appVectorStore, "")
//                )
                .call()
                .chatResponse();
        return chatResponse.getResult().getOutput().getText();
    }

    /**
     * AI健身报告功能（支持调用工具）
     *
     */
    public String doChatWithTools(String message, String chatId) {
        ChatResponse chatResponse = chatClient
                .prompt()
                .user(message)
                .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, chatId))
                // 开启日志，便于观察结果
                .advisors(new MyLoggerAdvisor())
                .toolCallbacks(allTools)
                .call()
                .chatResponse();
        return chatResponse.getResult().getOutput().getText();
    }

    /**
     * AI健身报告功能（调用 MCP 服务）
     *
     */
    public String doChatWithMcp(String message, String chatId) {
        ChatResponse chatResponse = chatClient
                .prompt()
                .user(message)
                .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, chatId))
                // 开启日志，便于观察结果
                .advisors(new MyLoggerAdvisor())
                .toolCallbacks(toolCallbackProvider)
                .call()
                .chatResponse();
        return chatResponse.getResult().getOutput().getText();
    }

    @JsonPropertyOrder({"message", "keywords","knowledgeSnippets"})
    record KeyWords(String message, List<String> keywords, List<String> knowledgeSnippets) {
    }

    public Flux<String> doChatWithStream(ChatDTO chatDTO) {
        String chatId = chatDTO.getChatId();
        String message = chatDTO.getMessage();
        Long uid = chatDTO.getUid();
//        String writePrompt = queryRewriter.doQueryRewrite(message);
//        KeyWords entity = keywordExtractionClient  // 使用专门的关键词提取客户端，避免保存消息
//                .prompt()
//                .system(SYSTEM_PROMPT)
//                .user(writePrompt)
//                .advisors(AppRagCustomAdvisorFactory.createAppRagCustomAdvisor(appVectorStore))
//                .call()
//                .entity(KeyWords.class);

//        // 优化提示词构建：使用 StringBuilder 和条件判断
//        StringBuilder systemPromptBuilder = new StringBuilder(FITNESS_SYSTEM_PROMPT);

//        if (entity != null) {
//            systemPromptBuilder.append("\n\n### 上下文信息 ###");
//
//            // 添加关键词（如果存在）
//            if (entity.keywords() != null && !entity.keywords.isEmpty()) {
//                systemPromptBuilder.append("\n关键词")
//                        .append(String.join("、", entity.keywords));
//            }
//
//            // 添加知识片段（如果存在）
//            if (entity.knowledgeSnippets() != null && !entity.knowledgeSnippets().isEmpty()) {
//                systemPromptBuilder.append("\n相关知识点：");
//                for (String snippet : entity.knowledgeSnippets()) {
//                    systemPromptBuilder.append("\n- ").append(snippet);
//                }
//            }
//        } else {
//            // 可选，添加空值处理日志
//            log.debug("No entity extracted for message: {}", message);
//        }

//        String finalSystemPrompt = systemPromptBuilder.toString();
        // 设置最大字数限制 - 测试阶段注释掉，不进行内容截断
        // int MAX_TOKENS = 1000;
        // AtomicInteger accumulatedLength = new AtomicInteger(0);
        // StringBuilder accumulatedContent = new StringBuilder();

        return chatClient.prompt()
                .user(message)
                .advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, chatId)
                        .param(VectorStoreChatMemoryAdvisor.TOP_K, 10))
//                .tools(allTools)
//                .toolContext(Map.of("userId",uid))
//                .system(finalSystemPrompt)
                .system(FITNESS_SYSTEM_PROMPT)
//                .tools(toolCallbackProvider)
//                .advisors(AppRagCustomAdvisorFactory.createAppRagCustomAdvisor(appVectorStore))
                .stream()
                .content();
                // 测试阶段注释掉内容截断逻辑
                /*
                .takeUntil(chunk -> {
                    int currentLength = accumulatedLength.get() + chunk.length();
                    if (currentLength > MAX_TOKENS) {
                        // 截断当前chunk并终止流
                        String truncatedChunk = chunk.substring(0, MAX_TOKENS - accumulatedLength.get());
                        accumulatedContent.append(truncatedChunk);
                        log.warn("内容超过限制，已截断至{}字", MAX_TOKENS);
                        return true;
                    } else {
                        accumulatedContent.append(chunk);
                        accumulatedLength.addAndGet(chunk.length());
                        return false;
                    }
                })
                .map(chunk -> {
                    // 如果已截断，追加提示信息
                    if (accumulatedLength.get() >= MAX_TOKENS) {
                        return accumulatedContent+ "\n\n内容已根据规范自动调整";
                    }
                    return chunk;
                });
                */
    }
}
